package com.example.minisqlite;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 最小WAL实现：支持BEGIN/PUT/DELETE/COMMIT，崩溃恢复时重放已提交事务。
 */
public class WalManager implements Closeable {

    private static final byte BEGIN = 1;
    private static final byte PUT = 2;
    private static final byte DELETE = 3;
    private static final byte COMMIT = 4;

    private final File file;
    private final RandomAccessFile raf;

    public WalManager(File file) throws IOException {
        this.file = file;
        this.raf = new RandomAccessFile(file, "rw");
        this.raf.seek(this.raf.length());
    }

    public synchronized void recover(StorageEngine storage) throws IOException, SQLException {
        raf.getChannel().force(true);
        raf.seek(0);
        long size = raf.length();
        List<WalOp> pending = new ArrayList<>();
        boolean inTxn = false;
        while (raf.getFilePointer() < size) {
            byte type = raf.readByte();
            switch (type) {
                case BEGIN:
                    pending.clear();
                    inTxn = true;
                    break;
                case PUT:
                    ensureInTxn(inTxn);
                    int tableId = raf.readInt();
                    long rowId = raf.readLong();
                    int len = raf.readInt();
                    byte[] payload = new byte[len];
                    raf.readFully(payload);
                    pending.add(new PutOp(tableId, rowId, payload));
                    break;
                case DELETE:
                    ensureInTxn(inTxn);
                    int delTable = raf.readInt();
                    long delRow = raf.readLong();
                    pending.add(new DeleteOp(delTable, delRow));
                    break;
                case COMMIT:
                    if (inTxn) {
                        applyPending(storage, pending);
                        pending.clear();
                        inTxn = false;
                    }
                    break;
                default:
                    throw new IOException("Corrupted WAL record: " + type);
            }
        }
        checkpoint();
    }

    private void applyPending(StorageEngine storage, List<WalOp> pending) throws IOException, SQLException {
        for (WalOp op : pending) {
            op.apply(storage);
        }
    }

    private void ensureInTxn(boolean inTxn) throws IOException {
        if (!inTxn) {
            throw new IOException("WAL log corruption: operation outside transaction");
        }
    }

    public synchronized void begin() throws IOException {
        raf.writeByte(BEGIN);
        raf.getChannel().force(false);
    }

    public synchronized void logPut(int tableId, long rowId, byte[] payload) throws IOException {
        raf.writeByte(PUT);
        raf.writeInt(tableId);
        raf.writeLong(rowId);
        raf.writeInt(payload.length);
        raf.write(payload);
        raf.getChannel().force(false);
    }

    public synchronized void logDelete(int tableId, long rowId) throws IOException {
        raf.writeByte(DELETE);
        raf.writeInt(tableId);
        raf.writeLong(rowId);
        raf.getChannel().force(false);
    }

    public synchronized void commit() throws IOException {
        raf.writeByte(COMMIT);
        raf.getChannel().force(true);
    }

    public synchronized void checkpoint() throws IOException {
        raf.setLength(0);
        raf.seek(0);
        raf.getChannel().force(true);
    }

    @Override
    public void close() throws IOException {
        raf.getChannel().force(true);
        raf.close();
    }

    private interface WalOp {
        void apply(StorageEngine storage) throws IOException, SQLException;
    }

    private static final class PutOp implements WalOp {
        private final int tableId;
        private final long rowId;
        private final byte[] payload;

        private PutOp(int tableId, long rowId, byte[] payload) {
            this.tableId = tableId;
            this.rowId = rowId;
            this.payload = payload;
        }

        @Override
        public void apply(StorageEngine storage) throws IOException {
            storage.putRow(tableId, rowId, payload);
        }
    }

    private static final class DeleteOp implements WalOp {
        private final int tableId;
        private final long rowId;

        private DeleteOp(int tableId, long rowId) {
            this.tableId = tableId;
            this.rowId = rowId;
        }

        @Override
        public void apply(StorageEngine storage) throws IOException {
            storage.deleteRow(tableId, rowId);
        }
    }
}
